package com.xx.doublexchat.common.user.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.xx.doublexchat.common.common.annotation.RedissonLock;
import com.xx.doublexchat.common.common.constant.RedisKey;
import com.xx.doublexchat.common.common.utils.AssertUtils;
import com.xx.doublexchat.common.common.utils.RedisUtils;
import com.xx.doublexchat.common.user.adapter.UserAdapter;
import com.xx.doublexchat.common.user.cache.ItemCache;
import com.xx.doublexchat.common.user.cache.UserCache;
import com.xx.doublexchat.common.user.cache.UserInfoCache;
import com.xx.doublexchat.common.user.cache.UserSummaryCache;
import com.xx.doublexchat.common.user.dao.BlackDao;
import com.xx.doublexchat.common.user.dao.ItemConfigDao;
import com.xx.doublexchat.common.user.dao.UserBackpackDao;
import com.xx.doublexchat.common.user.dao.UserDao;
import com.xx.doublexchat.common.user.domain.dto.ItemInfoDTO;
import com.xx.doublexchat.common.user.domain.dto.SummeryInfoDTO;
import com.xx.doublexchat.common.user.domain.entity.*;
import com.xx.doublexchat.common.user.domain.enums.BlackTypeEnum;
import com.xx.doublexchat.common.user.domain.enums.ItemEnum;
import com.xx.doublexchat.common.user.domain.enums.ItemTypeEnum;
import com.xx.doublexchat.common.user.domain.vo.request.BlackRequest;
import com.xx.doublexchat.common.user.domain.vo.request.ItemInfoRequest;
import com.xx.doublexchat.common.user.domain.vo.request.SummeryInfoRequest;
import com.xx.doublexchat.common.user.domain.vo.response.BadgesResponse;
import com.xx.doublexchat.common.user.domain.vo.response.UserInfoResponse;
import com.xx.doublexchat.common.user.event.UserBlackEvent;
import com.xx.doublexchat.common.user.event.UserRegisterEvent;
import com.xx.doublexchat.common.user.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserDao userDao;

    @Autowired
    private UserBackpackDao userBackpackDao;

    @Autowired
    private ItemCache itemCache;

    @Autowired
    private ItemConfigDao itemConfigDao;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private BlackDao blackDao;

    @Autowired
    private UserCache userCache;

    @Autowired
    private UserSummaryCache userSummaryCache;

    @Autowired
    private UserInfoCache userInfoCache;

    @Override
    @Transactional // 新开事务
    public Long register(User user) {
        boolean save = userDao.save(user);
        //用户注册事件
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, user));
        return user.getId();
    }

    @Override
    public UserInfoResponse getUserInfo(Long uid) {
        User user = userDao.getById(uid);
        Integer modifyNameCount = userBackpackDao.getCountByValidItemId(uid, ItemEnum.MODIFY_NAME_CARD.getId());
        return UserAdapter.buildUserInfo(user, modifyNameCount);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @RedissonLock(key = "#uid")
    public void modifyName(Long uid, String name) {
        User oldUser = userDao.getByName(name);
        AssertUtils.isEmpty(oldUser, "该名称已被使用,请重新输入");
        UserBackpack modifyNameItem = userBackpackDao.getFirstValidaItem(uid, ItemEnum.MODIFY_NAME_CARD.getId());
        AssertUtils.isNotEmpty(modifyNameItem, "改名卡次数已全部使用");
        //使用改名卡
        boolean success = userBackpackDao.useItem(modifyNameItem.getId());
        AssertUtils.isTrue(success, "改名失败请联系管理员");
        if (success) {
            // 使用改名卡成功--进行改名
            boolean updateSuccess = userDao.modifyName(uid, name);
            AssertUtils.isTrue(updateSuccess, "改名失败请联系管理员");
            //删除缓存
            userInfoCache.userInfoChange(uid);
        }
    }

    @Override
    public List<BadgesResponse> badges(Long uid) {
        // 通过缓存-获取所有徽章
        List<ItemConfig> itemConfigs = itemCache.getByType(ItemTypeEnum.BADGE.getType());
        // 获取用户徽章
        List<Long> ids = itemConfigs.stream().map(ItemConfig::getId).collect(Collectors.toList());
        List<UserBackpack> userBackpacks = userBackpackDao.getByItemIds(uid, ids);
        // 查询用户佩戴的徽章
        User user = userDao.getById(uid);
        return UserAdapter.buildBadgeResp(itemConfigs, userBackpacks, user);
    }

    @Override
    public void wearingBadges(Long uid, Long itemId) {
        // 确保徽章存在
        UserBackpack firstValidaItem = userBackpackDao.getFirstValidaItem(uid, itemId);
        AssertUtils.isNotEmpty(firstValidaItem, "您还没有这个徽章");
        // 确保这个物品是徽章
        ItemConfig item = itemConfigDao.getById(firstValidaItem.getItemId());
        AssertUtils.equal(item.getType(), ItemTypeEnum.BADGE.getType(), "只有徽章才能佩戴");
        // 佩戴徽章
        userDao.wearingBadges(uid, itemId);
        //删除用户缓存
        userInfoCache.userInfoChange(uid);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void black(BlackRequest req) {
        //拉黑用户
        Long uid = req.getUid();
        Black blackUser = new Black();
        blackUser.setType(BlackTypeEnum.UID.getType());
        blackUser.setTarget(uid.toString());
        blackDao.save(blackUser);
        //拉黑ip
        User user = userDao.getById(uid);
        blackIp(Optional.ofNullable(user.getIpInfo()).map(IpInfo::getCreateIp).orElse(null));
        blackIp(Optional.ofNullable(user.getIpInfo()).map(IpInfo::getUpdateIp).orElse(null));
        applicationEventPublisher.publishEvent(new UserBlackEvent(this, user));
    }

    /**
     * 获取用户聚合信息
     *
     * @param req
     * @return
     */
    @Override
    public List<SummeryInfoDTO> getSummeryUserInfo(SummeryInfoRequest req) {
        //判断是否需要重新获取---->通过lastModifyTime来判断
        //需要前端同步的uid
        List<Long> uidList = getNeedSyncUidList(req.getReqList());
        //首先尝试从redis获取，redis中没有去数据库查，再写入redis中
        Map<Long, SummeryInfoDTO> batch = userSummaryCache.getBatch(uidList);
        return req.getReqList()
                .stream()
                .map(a -> batch.containsKey(a.getUid()) ? batch.get(a.getUid()) : SummeryInfoDTO.skip(a.getUid()))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    /**
     * 获取需要刷新缓存的用户id
     *
     * @param reqList
     * @return
     */
    private List<Long> getNeedSyncUidList(List<SummeryInfoRequest.infoReq> reqList) {
        List<Long> needSyncUidList = new ArrayList<>();
        //通过id从缓存中获取用户信息-->最后更新时间
        List<Long> userModifyTime = userCache.getUserModifyTime(reqList.stream()
                .map(SummeryInfoRequest.infoReq::getUid).collect(Collectors.toList()));
        for (int i = 0; i < reqList.size(); i++) {
            SummeryInfoRequest.infoReq infoReq = reqList.get(i);
            //redis中的最后更新时间
            Long modifyTime = userModifyTime.get(i);
            //最后更新时间为空或者传过来的更新时间比redis中的小---->需要手动刷新redis
            if (Objects.isNull(infoReq.getLastModifyTime()) || (Objects.nonNull(modifyTime) && modifyTime > infoReq.getLastModifyTime())) {
                needSyncUidList.add(infoReq.getUid());
            }
        }
        return needSyncUidList;
    }

    /**
     * 获取物品聚合信息
     *
     * @param req
     * @return
     */
    @Override
    public List<ItemInfoDTO> getItemInfo(ItemInfoRequest req) {
        return null;
    }

    /**
     * 拉黑ip
     *
     * @param ip
     */
    private void blackIp(String ip) {
        if (StringUtils.isBlank(ip)) {
            return;
        }
        boolean existsFlag = blackDao.selectByIp(ip);
        if (existsFlag) {
            return;
        }
        Black blackUser = new Black();
        blackUser.setType(BlackTypeEnum.IP.getType());
        blackUser.setTarget(ip);
        blackDao.save(blackUser);
    }


}
